import subprocess
import re
import tkinter as tk
from tkinter import ttk, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import ctypes
import sys
from collections import defaultdict
import locale
import matplotlib.font_manager as fm  # 新增字体管理模块


class WiFiScannerApp:
    def __init__(self, root):
        self.setup_chinese_font()
        self.root = root
        self.root.title("Wlan扫描")
        self.root.geometry("1100x750")

        # 自动获取系统编码
        self.encoding = locale.getpreferredencoding()
        print(f"检测到系统编码: {self.encoding}")

        if not self.is_admin():
            self.request_admin()

        self.setup_ui()
        self.scan_wifi_networks()

    def setup_chinese_font(self):
        """配置中文字体，解决乱码问题"""
        try:
            # 尝试使用系统自带字体
            plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'Arial Unicode MS']
            plt.rcParams['axes.unicode_minus'] = False
        except:
            # 如果系统没有默认中文字体，尝试使用绝对路径
            try:
                font_path = 'C:/Windows/Fonts/msyh.ttc'  # 微软雅黑字体路径
                font_prop = fm.FontProperties(fname=font_path)
                plt.rcParams['font.sans-serif'] = [font_prop.get_name()]
                print(f"使用字体: {font_prop.get_name()}")
            except Exception as e:
                print(f"字体设置失败: {e}")
                messagebox.showwarning("字体警告", "系统中文字体缺失，图表可能显示异常")
    def is_admin(self):
        try:
            return ctypes.windll.shell32.IsUserAnAdmin()
        except:
            return False

    def request_admin(self):
        ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
        sys.exit()

    def setup_ui(self):
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 标题
        title_frame = ttk.Frame(main_frame)
        title_frame.pack(fill=tk.X)
        ttk.Label(title_frame, text="Wlan网络扫描", font=('Microsoft YaHei', 16, 'bold')).pack()

        # 控制按钮
        ctrl_frame = ttk.Frame(main_frame)
        ctrl_frame.pack(fill=tk.X, pady=5)
        ttk.Button(ctrl_frame, text="重新扫描", command=self.scan_wifi_networks).pack(side=tk.LEFT)

        # 表格框架
        table_frame = ttk.Frame(main_frame)
        table_frame.pack(fill=tk.BOTH, expand=True)

        # 表格
        self.tree = ttk.Treeview(
            table_frame,
            columns=('SSID', 'BSSID', '信号', '加密', '频道', '类型'),
            show='headings',
            height=15
        )

        # 配置列
        columns = {
            'SSID': {'width': 180, 'anchor': 'w'},
            'BSSID': {'width': 150, 'anchor': 'w'},
            '信号': {'width': 80, 'anchor': 'center'},
            '加密': {'width': 100, 'anchor': 'center'},
            '频道': {'width': 60, 'anchor': 'center'},
            '类型': {'width': 100, 'anchor': 'center'}
        }

        for col, config in columns.items():
            self.tree.heading(col, text=col)
            self.tree.column(col, **config)

        # 滚动条
        scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscroll=scrollbar.set)

        # 布局
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 图表框架
        chart_frame = ttk.Frame(main_frame)
        chart_frame.pack(fill=tk.BOTH, expand=True, pady=(10, 0))

        # 图表
        self.fig, (self.ax1, self.ax2) = plt.subplots(1, 2, figsize=(10, 4.5))
        self.fig.subplots_adjust(wspace=0.3)
        self.canvas = FigureCanvasTkAgg(self.fig, master=chart_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

    def scan_wifi_networks(self):
        """执行扫描"""
        for item in self.tree.get_children():
            self.tree.delete(item)

        output = self.get_scan_output()
        if not output:
            messagebox.showerror("错误", "无法获取Wlan数据")
            return

        networks = self.parse_output(output)
        self.display_results(networks)

    def get_scan_output(self):
        """获取扫描输出（自动处理编码）"""
        try:
            # 使用UTF-8尝试解码
            result = subprocess.run(
                ['netsh', 'wlan', 'show', 'networks', 'mode=bssid'],
                capture_output=True,
                text=True,
                encoding='utf-8',
                errors='ignore'  # 忽略无法解码的字符
            )
            return result.stdout
        except Exception as e:
            print(f"UTF-8解码失败: {e}")
            try:
                # 回退到系统默认编码
                result = subprocess.run(
                    ['netsh', 'wlan', 'show', 'networks', 'mode=bssid'],
                    capture_output=True,
                    text=True,
                    encoding=self.encoding,
                    errors='ignore'
                )
                return result.stdout
            except Exception as e:
                print(f"系统编码解码失败: {e}")
                return None

    def parse_output(self, output):
        """解析输出（兼容中英文）"""
        networks = []
        current_ssid = None
        current_bssid = None

        # 多语言正则表达式
        patterns = {
            'ssid': [
                re.compile(r'SSID\s*\d+\s*:\s*(.+)'),  # 英文
                re.compile(r'SSID\s*\d+\s*:\s*(.+)')  # 中文
            ],
            'bssid': [
                re.compile(r'BSSID\s*\d+\s*:\s*([0-9a-fA-F:]{17})')
            ],
            'signal': [
                re.compile(r'Signal\s*:\s*(\d+)%'),  # 英文
                re.compile(r'信号\s*:\s*(\d+)%')  # 中文
            ],
            'channel': [
                re.compile(r'Channel\s*:\s*(\d+)'),  # 英文
                re.compile(r'频道\s*:\s*(\d+)')  # 中文
            ],
            'auth': [
                re.compile(r'Authentication\s*:\s*(.+)'),  # 英文
                re.compile(r'身份验证\s*:\s*(.+)')  # 中文
            ],
            'cipher': [
                re.compile(r'Cipher\s*:\s*(.+)'),  # 英文
                re.compile(r'加密\s*:\s*(.+)')  # 中文
            ],
            'radio': [
                re.compile(r'Radio type\s*:\s*(.+)'),  # 英文
                re.compile(r'无线电类型\s*:\s*(.+)')  # 中文
            ]
        }

        for line in output.split('\n'):
            line = line.strip()

            # 匹配SSID
            for pattern in patterns['ssid']:
                if match := pattern.match(line):
                    current_ssid = {'ssid': match.group(1).strip(), 'bssids': []}
                    networks.append(current_ssid)
                    break

            # 匹配BSSID
            if current_ssid:
                for pattern in patterns['bssid']:
                    if match := pattern.match(line):
                        current_bssid = {
                            'bssid': match.group(1).lower(),
                            'signal': None,
                            'channel': None,
                            'auth': None,
                            'cipher': None,
                            'radio': None
                        }
                        current_ssid['bssids'].append(current_bssid)
                        break

            # 匹配其他字段
            if current_bssid:
                for field in ['signal', 'channel', 'auth', 'cipher', 'radio']:
                    for pattern in patterns[field]:
                        if match := pattern.match(line):
                            value = match.group(1).strip()
                            if field in ['signal', 'channel'] and value.isdigit():
                                current_bssid[field] = int(value)
                            else:
                                current_bssid[field] = value
                            break

        return networks

    def display_results(self, networks):
        """显示结果"""
        # 填充表格
        for network in networks:
            for bssid in network['bssids']:
                self.tree.insert('', tk.END, values=(
                    network['ssid'],
                    bssid['bssid'],
                    bssid.get('signal', 'N/A'),
                    bssid.get('cipher', 'N/A'),
                    bssid.get('channel', 'N/A'),
                    bssid.get('radio', 'N/A')
                ))

        # 更新图表
        self.update_charts(networks)

    def update_charts(self, networks):
        """更新图表"""
        self.ax1.clear()
        self.ax2.clear()

        # 准备数据
        signals = []
        channels = defaultdict(list)

        for network in networks:
            for bssid in network['bssids']:
                if (sig := bssid.get('signal')) is not None:
                    signals.append(sig)
                if (ch := bssid.get('channel')) is not None:
                    channels[ch].append(bssid.get('signal', 0))

        # 信号强度分布
        if signals:
            self.ax1.hist(signals, bins=range(0, 101, 5), color='#FF9800', edgecolor='white')
            self.ax1.set_xlabel('信号强度(%)')
            self.ax1.set_ylabel('AP数量')
            self.ax1.set_title('信号强度分布')
            self.ax1.grid(True, alpha=0.3)

        # 频道分布
        if channels:
            sorted_ch = sorted(channels.keys())
            avg_sig = [sum(channels[ch]) / len(channels[ch]) for ch in sorted_ch]

            bars = self.ax2.bar(sorted_ch, avg_sig, color='#8BC34A', width=0.6)
            self.ax2.bar_label(bars, fmt='%.0f%%', padding=2, fontsize=8)
            self.ax2.set_xlabel('无线频道')
            self.ax2.set_ylabel('平均信号(%)')
            self.ax2.set_title('各频道信号质量')
            self.ax2.grid(True, alpha=0.3)

        self.canvas.draw()


if __name__ == "__main__":
    try:
        root = tk.Tk()
        app = WiFiScannerApp(root)

        # 设置UI缩放（适应高DPI屏幕）
        if sys.platform == 'win32':
            from ctypes import windll

            windll.shcore.SetProcessDpiAwareness(1)

        root.mainloop()
    except Exception as e:
        messagebox.showerror("致命错误", f"程序崩溃: {str(e)}")